package com.ody.util.code.model;

import com.google.common.collect.Maps;
import com.odianyun.util.exception.ExceptionUtils;
import com.odianyun.util.value.ValueUtils;
import com.ody.util.code.Const;
import com.ody.util.code.Main;
import java.awt.*;
import java.awt.event.*;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import javax.swing.*;
import javax.swing.table.*;
import org.apache.logging.log4j.util.TriConsumer;
import org.springframework.util.StringUtils;

public class ModelGui extends POModelProvider {
    private List<POBean> poList;
    private boolean completed;
    
    public ModelGui(POModelProvider target) {
        this.poList = target.getPOList();
        try {
            render();
        
            while (!completed) {
                Thread.sleep(500);
            }
        } catch (Exception e) {
            throw ExceptionUtils.wrap2Runtime(e);
        }
    }
    
    protected List<POBean> getPOList() {
        return poList;
    }
    
    private void render() throws Exception {
        JFrame.setDefaultLookAndFeelDecorated(true);
        JFrame frame = new JFrame(Const.CODE_GENERATOR_TITLE + Const.VERSION);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel root = new JPanel();
        root.setLayout(new BoxLayout(root, BoxLayout.PAGE_AXIS));
        root.setBackground(Color.LIGHT_GRAY);
        if (System.getProperty("os.name").contains("Window")) {
            UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
        }
        
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        URL url = Main.class.getClassLoader().getResource("logo.png");
        frame.setIconImage(toolkit.createImage(url));
    
        JTabbedPane tabs = new JTabbedPane();
        tabs.setBackground(Color.BLUE);
        root.add(tabs);
    
        for (POBean po : poList) {
            JComponent tab = renderModelTabPanel(po);
            tabs.add(StringUtils.hasText(po.getTitle()) ? po.getTitle() : po.getName(), tab);
        }
    
        JPanel btnPanel = new JPanel();
        btnPanel.setBackground(Color.GREEN);
        
        JButton confirm = new JButton("确定");
        confirm.setBackground(Color.BLUE);
        confirm.addActionListener((e) -> {
            frame.dispose();
            completed = true;
        });
        btnPanel.add(confirm);
        
        root.add(btnPanel);
    
        frame.add(root);
        frame.pack();
    
        double d1 = Toolkit.getDefaultToolkit().getScreenSize().getWidth();
        double d2 = Toolkit.getDefaultToolkit().getScreenSize().getHeight();
        frame.setLocation((int) (d1 - frame.getWidth()) / 2, (int) (d2 - frame.getHeight()) / 2);
        frame.setVisible(true);
    }
    
    private JComponent renderModelTabPanel(POBean po) {
        final Map<Integer, String> headerMap = Maps.newLinkedHashMap();
        headerMap.put(0, "字段名");
        headerMap.put(1, "列名");
        headerMap.put(2, "中文");
        headerMap.put(3, "是否必填");
        headerMap.put(4, "是否查询");
        headerMap.put(5, "是否列表显示");
        headerMap.put(6, "是否可编辑");
        headerMap.put(7, "是否导入");
        headerMap.put(8, "是否导出");
        Map<Integer, Integer> sizeMap = new HashMap<>();
        sizeMap.put(0, 120);
        sizeMap.put(1, 120);
        sizeMap.put(2, 220);
        sizeMap.put(3, 130);
        sizeMap.put(4, 150);
        sizeMap.put(5, 200);
        sizeMap.put(6, 180);
        sizeMap.put(7, 180);
        sizeMap.put(8, 180);
        final Map<Integer, Class<?>> typeMap = Maps.newHashMap();
        typeMap.put(0, String.class);
        typeMap.put(1, String.class);
        typeMap.put(2, String.class);
        typeMap.put(3, Boolean.class);
        typeMap.put(4, Boolean.class);
        typeMap.put(5, Boolean.class);
        typeMap.put(6, Boolean.class);
        typeMap.put(7, Boolean.class);
        typeMap.put(8, Boolean.class);
        final Map<Integer, Function<FieldBean, String>> getterMap = Maps.newHashMap();
        getterMap.put(0, FieldBean::getName);
        getterMap.put(1, FieldBean::getColumn);
        getterMap.put(2, FieldBean::getTitle);
        final Map<Integer, TriConsumer<POBean, FieldBean, Object>> setterMap = Maps.newHashMap();
        setterMap.put(0, (p, f, v) -> f.setName(ValueUtils.convert(v, String.class)));
        setterMap.put(1, (p, f, v) -> f.setColumn(ValueUtils.convert(v, String.class)));
        setterMap.put(2, (p, f, v) -> f.setTitle(ValueUtils.convert(v, String.class)));
        setterMap.put(3, (p, f, v) -> f.setNullable(!ValueUtils.convert(v, boolean.class)));
        setterMap.put(4, (p, f, v) -> {
            if (ValueUtils.convert(v, boolean.class)) {
                p.getSearchFields().add(f.clone());
            } else {
                for (Iterator<FieldBean> ite = p.getSearchFields().iterator(); ite.hasNext();) {
                    if (ite.next().getName().equals(f.getName())) {
                        ite.remove();
                    }
                }
            }
        });
        setterMap.put(5, (p, f, v) -> {
            if (ValueUtils.convert(v, boolean.class)) {
                p.getTableFields().add(f.clone());
            } else {
                for (Iterator<FieldBean> ite = p.getTableFields().iterator(); ite.hasNext();) {
                    if (ite.next().getName().equals(f.getName())) {
                        ite.remove();
                    }
                }
            }
        });
        setterMap.put(6, (p, f, v) -> {
            if (ValueUtils.convert(v, boolean.class)) {
                p.getEditFields().add(f.clone());
            } else {
                for (Iterator<FieldBean> ite = p.getEditFields().iterator(); ite.hasNext();) {
                    if (ite.next().getName().equals(f.getName())) {
                        ite.remove();
                    }
                }
            }
        });
        setterMap.put(7, (p, f, v) -> {
            if (ValueUtils.convert(v, boolean.class)) {
                p.getImportFields().add(f.clone());
            } else {
                for (Iterator<FieldBean> ite = p.getImportFields().iterator(); ite.hasNext();) {
                    if (ite.next().getName().equals(f.getName())) {
                        ite.remove();
                    }
                }
            }
        });
        setterMap.put(8, (p, f, v) -> {
            if (ValueUtils.convert(v, boolean.class)) {
                p.getExportFields().add(f.clone());
            } else {
                for (Iterator<FieldBean> ite = p.getExportFields().iterator(); ite.hasNext();) {
                    if (ite.next().getName().equals(f.getName())) {
                        ite.remove();
                    }
                }
            }
        });
    
        FieldTableModel model = new FieldTableModel(po, po.getFields(), typeMap, headerMap, setterMap);
        JTable table = new JTable(model) {
            // 列表内tip
            @Override
            public String getToolTipText(MouseEvent event) {
                Point p = event.getPoint();
                int rowIndex = rowAtPoint(p);
                int colIndex = columnAtPoint(p);
                if (getterMap.containsKey(colIndex)) {
                    FieldBean field = po.getFields().get(rowIndex);
                    return getterMap.get(colIndex).apply(field);
                }
                return super.getToolTipText(event);
            }
        };
        table.setTableHeader(new JTableHeader(table.getTableHeader().getColumnModel()) {
            // 表头tip
            public String getToolTipText(MouseEvent event) {
                int column = this.columnModel.getColumnIndexAtX(event.getX());
                return headerMap.get(column);
            }
        });
        
        table.setRowHeight(30);
        TableColumn tableColumn = null;
        for (int i = 0; i < table.getColumnCount(); i++) {
            tableColumn = table.getColumnModel().getColumn(i);
            if (sizeMap.containsKey(i)) {
                tableColumn.setPreferredWidth(sizeMap.get(i));
            } else {
                tableColumn.setPreferredWidth(100);
            }
            Class<?> columnType = typeMap.get(i);
            final int currentColIndex = i;
            TableColumn currentColumn = tableColumn;
            if (columnType != null && Boolean.class.isAssignableFrom(columnType)) {
                currentColumn.setHeaderRenderer(new TableCellRenderer() {
                    private JCheckBox checkbox;
                    {
                        checkbox = new JCheckBox();
                        checkbox.setSelected(true);
                        table.getTableHeader().addMouseListener(new MouseAdapter() {
                            @Override
                            public void mouseClicked(MouseEvent e) {
                                if (e.getClickCount() > 0) {
                                    int selectColumn = table.getTableHeader().columnAtPoint(e.getPoint());
                                    if (currentColIndex == selectColumn) {
                                        boolean value = !checkbox.isSelected();
                                        checkbox.setSelected(value);
                                        table.getTableHeader().repaint();
                                        // Cascade select field beans
                                        for (FieldBean field : po.getFields()) {
                                            setterMap.get(currentColIndex).accept(po, field, checkbox.isSelected());
                                        }
                                    }
                                }
                            }
                        });
                    }
    
                    @Override
                    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
                        checkbox.setText(headerMap.get(column));
                        checkbox.setHorizontalAlignment(SwingConstants.CENTER);
                        checkbox.setForeground(table.getTableHeader().getForeground());
                        checkbox.setBackground(table.getTableHeader().getBackground());
                        checkbox.setFont(table.getTableHeader().getFont());
                        checkbox.setBorder(UIManager.getBorder("TableHeader.cellBorder"));
                        return checkbox;
                    }
                });
            }
        }
        JPanel tabBody = new JPanel();
    
        JPanel namePanel = new JPanel();
        tabBody.setLayout(new BoxLayout(tabBody, BoxLayout.PAGE_AXIS));
        JLabel nameLabel = new JLabel(po.getName() + "中文:");
        JTextField nameInput = new JTextField(po.getTitle(), 50);
        namePanel.add(nameLabel);
        namePanel.add(nameInput);
        
        JScrollPane pane = new JScrollPane();
        pane.setViewportView(table);
    
        tabBody.add(namePanel);
        tabBody.add(pane);
        return tabBody;
    }
    
    static class FieldTableModel extends AbstractTableModel {
        private POBean po;
        private List<FieldBean> fields;
        private Map<Integer, Class<?>> typeMap;
        private Map<Integer, String> headerMap;
        private Map<Integer, TriConsumer<POBean, FieldBean, Object>> setterMap;
        
        public FieldTableModel(POBean po, List<FieldBean> fields, Map<Integer, Class<?>> typeMap,
                               Map<Integer, String> headerMap, Map<Integer, TriConsumer<POBean, FieldBean, Object>> setterMap) {
            this.po = po;
            this.fields = fields;
            this.typeMap = typeMap;
            this.headerMap = headerMap;
            this.setterMap = setterMap;
        }
    
        @Override
        public boolean isCellEditable(int rowIndex, int columnIndex) {
            return columnIndex > 1;
        }
    
        @Override
        public String getColumnName(int column) {
            return headerMap.get(column);
        }
    
        @Override
        public int getRowCount() {
            return fields.size();
        }
    
        @Override
        public int getColumnCount() {
            return headerMap.size();
        }
    
        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return typeMap.get(columnIndex);
        }
    
        @Override
        public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
            if (setterMap.containsKey(columnIndex)) {
                setterMap.get(columnIndex).accept(po, fields.get(rowIndex), aValue);
            }
        }
    
        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            FieldBean field = fields.get(rowIndex);
            switch (columnIndex) {
                case 0: {
                    return field.getName();
                }
                case 1: {
                    return field.getColumn();
                }
                case 2: {
                    return field.getTitle();
                }
                case 3: {
                    return !field.isNullable();
                }
                case 4: {
                    return po.getSearchFields() != null && po.getSearchFields().stream().anyMatch(f -> f.getName().equals(field.getName()));
                }
                case 5: {
                    return po.getTableFields() != null && po.getTableFields().stream().anyMatch(f -> f.getName().equals(field.getName()));
                }
                case 6: {
                    return po.getEditFields() != null && po.getEditFields().stream().anyMatch(f -> f.getName().equals(field.getName()));
                }
                case 7: {
                    return po.getImportFields() != null && po.getImportFields().stream().anyMatch(f -> f.getName().equals(field.getName()));
                }
                case 8: {
                    return po.getExportFields() != null && po.getExportFields().stream().anyMatch(f -> f.getName().equals(field.getName()));
                }
            }
            return null;
        }
    }
}
